package com.kp.fintech.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;


@Component
@Slf4j
@PropertySource({"classpath:application-officedev.yml"}) //读取配置文件
public class GatewayFilterConfig implements GlobalFilter, Ordered {

    private static final Logger logger = LoggerFactory.getLogger(GatewayFilterConfig.class);
    @Value("${ignoresUriFilter}")
    private String IGNORE_URI_GLOBAL_FILTER;

    @Value("${tokenValidTime}")
    private  static String tokenValidTime ;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private LoadBalancerClient loadBalancer;

    @Value("${tokenAccessTimeOut}")
    private  String  tokenAccessTimeOut;

    @Value("${isValidateSubUrl}")
    private boolean isValidateSubUrl ;



    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String requestUrl = exchange.getRequest().getPath().value();

        //1 login服务生成无需验证权限, 返回token
        String[] ignoreUrlArray = IGNORE_URI_GLOBAL_FILTER.split(",");
        for (int i = 0; i < ignoreUrlArray.length; i++) {
            if (ignoreUrlArray[i].trim().equals(requestUrl)) return chain.filter(exchange);
        }

        //获取客户端的tokenNo
        String tokenNo = getToken(exchange);
        //2 检查token是否存在
        if (StringUtils.isBlank(tokenNo)) {
            return erroErrorMono(exchange, 10000, "请求参数错误，没有带token参数");
        } else {
            //3 判断是否是有效的token
            Map<String, Object> map = null; //通过客户端的tokenNo为键值获取redis数据
            try {
                map = redisTemplate.opsForHash().entries(tokenNo);
            } catch (Exception e) {
                return erroErrorMono(exchange, 10001, "缓存中获取token异常，无法获取token"); //缓存中token异常，无法获取token
            }

            if (map.size() == 0) {
                return erroErrorMono(exchange, 10002, "验证token过期导致失败，请重新登录"); //缓存中token过期，无法获取token
            } else {
                String redisTokenNo = (String) map.get("tokenNo");
                String redisUserName = (String) map.get("userName");
                Long timeStamp = (Long) map.get("timeStamp");

                ServiceInstance serviceInstance = loadBalancer.choose("fintech-oauth");
                String uriStr = "http://" + serviceInstance.getServiceId() + "/oauth/validate-token";
                logger.debug("调用微服务fintech-oauth的uri为：{}", uriStr);

                Map<String, String> mapParam = new HashMap<>();
                mapParam.put("tokenNo", redisTokenNo);
                mapParam.put("userName", redisUserName);
                //调用认证微服务，验证令牌号正确否
                Boolean isTrue = restTemplate.postForObject(uriStr, mapParam, Boolean.class);
                logger.debug("*****令牌验证通过否：{}", isTrue);
                if (!isTrue) {
                    return erroErrorMono(exchange, 10003, "校验token失败啦");
                }

                Long timeLong = redisTemplate.getExpire(tokenNo,TimeUnit.MINUTES);
                logger.debug("token 保存时间为：{} 过期时间为： {}",timeStamp, timeLong);
                //4 比较token保存时间与当前时间，满足条件重新刷新token
                if (System.currentTimeMillis() - timeStamp > Long.parseLong(tokenAccessTimeOut)) {
                    try {
                        map.replace("timeStamp",System.currentTimeMillis());
                        redisTemplate.opsForHash().putAll(tokenNo,map);
                        /* 取出记录的时间戳与当前时间对比 若大于半小时则更新缓存,自动续期 */
                        redisTemplate.expire(tokenNo,Long.parseLong(tokenValidTime), TimeUnit.MINUTES);
                    } catch (NumberFormatException e) {
                        return erroErrorMono(exchange, 10004, "更新缓存token失败啦");
                    }
                }
                //验证请求URL权限
                if(isTrue == isValidateSubUrl ) {
                    if (!validateSubUrl(mapParam, requestUrl)) {
                        logger.info("客户访问权限url验证不通过{}", requestUrl);
                        return erroErrorMono(exchange, 10005, "没有访问权限，抱歉!");
                    }
                }
            }

/*            //获取客户端请求参数
            String method = exchange.getRequest().getMethodValue();
            if ("POST".equals(method)) {
                ServerHttpRequest requestPer = validatePostParam(exchange, chain);
                return chain.filter(exchange.mutate().request(requestPer).build());
            } else if ("GET".equals(method)) {
                Map requestQueryParams = exchange.getRequest().getQueryParams();
                //TODO 得到Get请求的请求参数后，做你想做的事
                return chain.filter(exchange);
            }*/

            return chain.filter(exchange);
        }
    }

    /**
     * 获取token
     */
    private String getToken(ServerWebExchange exchange) {
        String tokenStr = exchange.getRequest().getHeaders().getFirst("token");
        if (StringUtils.isBlank(tokenStr)) {
            return null;
        }
        return tokenStr;
    }


    /**
     * 无效的token
     */
    private Mono<Void> erroErrorMono(ServerWebExchange exchange, int code, String msg) {
        JSONObject json = new JSONObject();
        json.put("code", code);
        json.put("msg", msg);
        return buildReturnMono(json, exchange);
    }


    private Mono<Void> buildReturnMono(JSONObject json, ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        byte[] bits = json.toJSONString().getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        //response.setStatusCode(HttpStatus.UNAUTHORIZED);
        //指定编码，否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");

        return response.writeWith(Mono.just(buffer));
    }


    private DataBuffer stringBuffer(String value) {
        byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
        NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
        DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
        buffer.write(bytes);
        return buffer;
    }


   private Boolean validateSubUrl(Map<String, String> uriParam,String requestUrl) {

        List<String> subUrlListsRedis = redisTemplate.opsForList().range(uriParam.get("userName")+"_suburl",0,-1);

        //缓存中没有suburl集合，远程调用服务获取
        if(subUrlListsRedis.size() <= 0){
            ServiceInstance serviceInstance = loadBalancer.choose("fintech-admin");
            logger.debug("服务名称：{}", serviceInstance.getServiceId());
            String uriStr = "http://" + serviceInstance.getServiceId() + "/sys/user/findSubUrl";
            logger.info("调用微服务fintech-oauth的uri为：{}", uriStr);

            try {
                List<String> subUrlLists  = restTemplate.postForObject(uriStr, uriParam, List.class);
                for(String subUrlStr:subUrlLists){
                    if(requestUrl.equals(subUrlStr)) return true;
                }
                return false ;

            } catch (RestClientException e) {
                e.printStackTrace();
                return false;
            }
        }else{
            for(String subUrlStr:subUrlListsRedis){
                if(requestUrl.equals(subUrlStr)) return true;
            }
            return false ;

        }
    }

    /*
     * 验证客户权限
     * 从Flux<DataBuffer>中获取字符串的方法
     */
    private ServerHttpRequest validatePostParam(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest serverHttpRequest = exchange.getRequest();

        //从请求里获取Post请求体
        //获取请求体
        Flux<DataBuffer> body = serverHttpRequest.getBody();
        AtomicReference<String> bodyRef = new AtomicReference<>();
        body.subscribe(buffer -> {
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
            DataBufferUtils.release(buffer);
            bodyRef.set(charBuffer.toString());
        });

        String bodyStr = bodyRef.get();
        //TODO 得到Post请求的请求参数后，做你想做的事
        HashMap hashMap = JSON.parseObject(bodyStr, HashMap.class);
        String permission = (String) hashMap.get("permission");

        //下面的将请求体再次封装写回到request里，传到下一级，否则，由于请求体已被消费，后续的服务将取不到值
        URI uri = serverHttpRequest.getURI();
        ServerHttpRequest request = serverHttpRequest.mutate().uri(uri).build();
        DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
        Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);

        request = new ServerHttpRequestDecorator(request) {
            @Override
            public Flux<DataBuffer> getBody() {
                return bodyFlux;
            }
        };
        //封装request，传给下一级
        return request;

    }

    @Override
    public int getOrder() {
        return 10101;  //在OrderedGatewayFilter之后执行该filter，保证路径和权限判断逻辑
    }
}